﻿#pragma once

#include <cstddef>

namespace Lisp
{

  class RefCounted
  {
  public:
    RefCounted() : m_refCount(0) {}
    virtual ~RefCounted() {}

    const RefCounted *Acquire() const
    {
      m_refCount++;
      return this;
    }
    int Release() const { return --m_refCount; }
    int RefCount() const { return m_refCount; }

  private:
    RefCounted(const RefCounted &);            // no copy ctor
    RefCounted &operator=(const RefCounted &); // no assignments

    mutable int m_refCount;
  };

  template <class T>
  class RefCountedPtr
  {
  public:
    RefCountedPtr() : m_object(0) {}

    RefCountedPtr(T *object) : m_object(0)
    {
      Acquire(object);
    }

    template <class U>
    RefCountedPtr(const RefCountedPtr<U> &rhs) : m_object(0)
    {
      Acquire(rhs.get());
    }

    RefCountedPtr(const RefCountedPtr &rhs) : m_object(0)
    {
      Acquire(rhs.m_object);
    }

    const RefCountedPtr &operator=(const RefCountedPtr &rhs)
    {
      Acquire(rhs.m_object);
      return *this;
    }

    bool operator==(const RefCountedPtr &rhs) const
    {
      return m_object == rhs.m_object;
    }

    bool operator!=(const RefCountedPtr &rhs) const
    {
      return m_object != rhs.m_object;
    }

    operator bool() const
    {
      return m_object != NULL;
    }

    ~RefCountedPtr()
    {
      Release();
    }

    T *operator->() const { return m_object; }
    T *get() const { return m_object; }

  private:
    void Acquire(T *object)
    {
      if (object != NULL)
      {
        object->Acquire();
      }
      Release();
      m_object = object;
    }

    void Release()
    {
      if ((m_object != NULL) && (m_object->Release() == 0))
      {
        delete m_object;
      }
    }

    T *m_object;
  };

} // namespace Lisp
